Aula 9: Pêndulo via deteção de movimento

Objetivos

  • Capturar o traçado de um pêndulo a partir de um vídeo
  • Transformar o traçado na representação $\theta(t)$ da posição do pêndulo.
  • Fazer o ajuste do modelo de EDO a esses dados.
  • Examinar a dependência do período em relação à amplitude do ângulo de oscilação.

Módulo de detecção e traçamento do movimento

O módulo python de detecção de movimento foi feito com base no código descrito na página Basic motion detection and tracking with Python and OpenCV.

Esse código utiliza o pacote OpenCV do python e ferramentas de manipulação de imagens do próprio autor do script, contidas no pacote imutils (disponível em github/jrosebr1/imutils e em PyPI - imutils).

O código original

O código original motion_detector.py na página Basic motion detection and tracking with Python and OpenCV abre três janelas de vídeo e, por conta dessas janelas, não funciona dentro do Jupyter.

Fiz modificações para ele funcionar como um módulo a ser importado pelo Jupyter.

Modificando o script para criar um módulo

Capturando as coordenadas do movimento

No código original, o movimento é apenas exibido em um vídeo.

Modifiquei-o para guardar esse movimento em uma lista, contendo o número de cada quadro analisado e os dados espaciais da caixa de entorno do objeto em movimento, no quadro em questão.

Os dados da caixa de entorno contém as coordenadas horizontal e vertical (em pixels) do canto superior esquerdo da caixa (retângulo) que contém o objeto detectado, seguidas da largura e da altura da caixa.

Observação: É importante ressaltar, na intepretação do resultado, que o ponto de coordenada $(0,0)$ corresponde ao canto superior esquerdo.

Importando o módulo

Primeiro, verifico o diretório do módulo.

In [2]:
listdir(path.join('..', 'codigos', 'deteccao_de_movimento'))
Out[2]:
['.ipynb_checkpoints', '__pycache__', 'movimento.py']

Em seguida, adiciono este caminho ao PATH e importo o módulo

In [3]:
sys.path.insert(0, path.abspath(path.join(getcwd(),'..', 'codigos', 'deteccao_de_movimento')))

import movimento

Ajuda do módulo

In [4]:
help(movimento)
Help on module movimento:

NAME
    movimento - Detecção e traçamento do movimento de objetos em vídeos.

DESCRIPTION
    Este código possui uma função de detecção e traçamento do 
    movimento de objetos em vídeos. 
    
    O código identifica objetos se movendo e enquadra o objeto com uma
    "caixa de entorno". Para cada quadro em que um objeto em movimento,
    o número do quadro, junto com os dados dessa caixa de entorno 
    (coordenadas horizontal e vertical do canto inferior da caixa e a 
    largura e a altura da caixa) são registrados em uma lista. Essa 
    lista é retornada pela função ao final da análise.
    
    Isso é implementado por uma única função, que possui outros argumentos
    de configuração e funcionalidades:
    
        * detector_de_movimento: recebe o caminho para um arquivo de video
            que será analisado visando detectar e traçar o movimento de um 
            ou mais objetos. 
            
            Retorna o número de quadros por segundo do video e os dados 
            sobre o andamento da caixa de entorno dos objetos em movimento. 
            
            Dependendo dos argumentos, pode gravar em disco ou exibir
            na tela os videos usados ao longo do processamento. Outras 
            opções ajustam a área mínima para um objeto ser identificado, 
            o nível de corte da escala de cinzas usada na detecção do 
            objeto e os quadros inicial e final para o tratamento.
    
    Para mais informações, veja o `help` da função `detector_de_movimento`.
    
    Também é possível usar o código como um script, a partir da linha de
    comando. Veja as informações sobre como passar os argumentos através do
    comando de linha
    ```bash
    detector_de_movimento --help
    ```
    
    Exemplos de uso como script:
        1) Exibindo, na tela do computador, os vídeos de processamento
    
            $ python movimento.py --video video.mov -e
    
        2) Alterando alguns parâmetros, gravando o video processado e 
        exibindo os vídeos de processamento:
    
            $ python movimento.py --video video.mov -o video_tratado.avi
                --area_min 200 --nivel_de_corte 50 -e
    
    O código usa o pacote `opencv` e o módulo `imutils`, além do módulo
    padrão `argparse`.
    
    Baseado no script disponível em [Basic motion detection and tracking with Python and OpenCV](https://www.pyimagesearch.com/2015/05/25/basic-motion-detection-and-tracking-with-python-and-opencv/).

FUNCTIONS
    detector_de_movimento(video, width=512, area_min=500, nivel_de_corte=25, quadro_inicial=1, quadro_final=-1, video_tracado=None, video_cinza=None, video_pb=None, exibe_videos=False)
        Detecta e traça o movimento de objetos em vídeos.
        
        Esta função usa um método simples de contraste de cada quadro
        com o quadro inicial para detectar a alteração ocorrida e
        inferir e acompanhar a posição de um objeto que se moveu ou 
        que não estava presente na imagem original. 
        
        Se o objeto já aparece na imagem inicial e se desloca, um 
        "fantasma" do objeto é constantemente detectado. O ideal é que 
        o objeto não apareça inicialmente no vídeo.
        
        O algoritmo de detecção é baseado nos seguintes passos:
        
            - O primeiro quadro do filme é transformado em escala
             de cinza (de 0 a 255, i.e. de preto a branco), gerando 
             um quadro denominado `firstFrame`.
        
            - Cada quadro subsequente também é transformado em escala 
            de cinza (denominado `gray`) e um novo quadro é formado com 
            a diferença entre esse quadro e o primeiro quadro: 
            `tresh = gray - firstFrame`.
        
            - Com base em um `nivel_de_cinza`, entre 0 e 255, essa 
            diferença é transformada em uma imagem `tresh` em preto 
            e branco, ficando cada *pixel* preto, se o cinza estiver 
            abaixo desse nível, ou branco, se estiver acima.
        
            - Curvas de contorno entre as partes em preto e em branco 
            da imagem `tresh` são identificadas pelo `opencv`.
        
            - Para cada curva envolvendo uma área maior do que uma área 
            mínima `area_min`, uma *caixa de entorno* é registrada.
        
        Entrada:
        --------
            video: string
                Uma string com o nome (incluindo o caminho) do arquivo 
                de vídeo a ser tratado.
            
            area_min: ponto flutuante
                A área mínima (em número de pixels) necessária para 
                que um objeto seja identificado. Por exemplo, um
                pequeno retângulo de 20 por 20 pixels tem 400 pixels
                de área.
        
            nivel_de_corte: ponto flutuante
                O nível de corte para o contraste, em uma escala de 0 a 255,
                para determinar, em cada pixel da imagem, se há diferente 
                entre o quadro atual e o quadro inicial ou não.
                                                
            video_tracado: string
                Uma string com o nome (incluindo o caminho) do arquivo 
                de vídeo a ser gravado contendo o resultado do 
                processamento. O vídeo inclui textos indicando o número
                de cada quadro, o tempo decorrido, o estado do ambiente
                (se ocupado ou desocupado) e com uma caixa de contorno
                em volta de cada objeto detectado.
        
            video_cinza: string
                Uma string com o nome (incluindo o caminho) do arquivo 
                de vídeo a ser gravado contendo a diferença (pixel a pixel)
                entre os níveis de cinza entre o quadro atual e o quadro
                inicial, que forma a base para a detecção.
            
            video_pb: string
                Uma string com o nome (incluindo o caminho) do arquivo 
                de vídeo a ser gravado, em preto e branco, contendo 
                a versão "estourada" do `video_cinza` descrito acima.
                O estouro é feito com base no `nivel_de_cinza`. 
        
            exibe_videos = Boolean
                Se `True`, exibe os vídeos envolvidos no tratamento, a
                saber, um vídeo com o contraste inicial entre o quadro 
                atual e o inicial, um vídeo com esse contraste saturado 
                e um vídeo com o processamento final, descrito na
                opção `video_tracado`.
        
        Saída:
        ------
            fps: ponto flutuante
                O número de quadros por segundo do vídeo.
        
            num_quadros: ponto flutuante
                O número total de quadros examinados.
        
            tracado: lista
                Uma lista de listas, onde o primeiro item da lista 
                é a lista de strings ['n', 'x', 'y', 'l', 'h'], o segundo 
                item é a lista de strings `['quadro', 'abscissa(px)', 
                'ordenada(px)', 'largura(px)', 'altura(px)']` e outros 
                itens são listas contenda, no ordem, inteiros indicando
                o número do quadro correspondente, a abscissa e a ordenada
                do cando esquerdo inferior da caixa de entorno do objeto
                identificado no quadro e a largura e a altura dessa caixa
                de entorno.
        
                Quando mais de um objeto é identificado em um mesmo quadro,
                os dados da caixa de entorno de cada objeto são incluídos
                em itens diferentes da lista `tracado`, contendo o mesmo
                número de quadro.
                
        Exemplos:
        --------
            1) Gravando o vídeo processado:
            
                detector_de_movimento("video.mp4",  
                                      video_tracado = "video_tratado.avi")
        
            2) Alterando alguns parâmetros e exibindo os vídeos de 
            processamento:
        
                detector_de_movimento("video.mp4", area_min = 200, 
                                      nivel_de_corte = 50,
                                      exibe_videos = True)

DATA
    __homepage__ = 'http://github.com/rmsrosa/jupyterbookmaker'
    __license__ = 'GNU GPLv3'
    __status__ = 'beta'

VERSION
    0.3.1

AUTHOR
    Ricardo M. S. Rosa <rmsrosa@gmail.com>

FILE
    /Users/rrosa/Dropbox/Documents/math-atual/im/modmat-2019/modmatrepository/codigos/deteccao_de_movimento/movimento.py


Sobre o método de captura do movimento

A captura do movimento é feita através de um simples método de contraste com a imagem do primeiro quadro do filme. Os seguintes passos são feitos:

  • O primeiro quadro do filme é transformado em escala de cinza (de 0 a 255, i.e. de preto a branco), gerando um quadro denominado firstFrame.

  • Cada quadro subsequente também é transformado em escala de cinza (denominado gray) e um novo quadro é formado com a diferença entre esse quadro e o primeiro quadro: tresh = gray - firstFrame.

  • Com base em um nivel_de_cinza, entre 0 e 255, essa diferença é transformada em uma imagem tresh em preto e branco, ficando cada pixel preto, se o cinza estiver abaixo desse nível, ou branco, se estiver acima.

  • Curvas de contorno entre as partes em preto e em branco da imagem tresh são identificadas pelo opencv.

  • Para cada curva envolvendo uma área maior do que uma área mínima area_min, uma caixa de entorno é registrada.

Vídeos gravados

Foram três vídeos gravados, dois de pêndulos com $70\,\texttt{cm}$ de haste e um com $40\,\texttt{cm}$.

Estão no subdiretório dados/pendulo_movimento.

In [5]:
listdir(videos_dir)
Out[5]:
['.DS_Store',
 'pendulo_40cm_reduzido.mov',
 'pendulo_40cm_reduzido_tracado_inicio.avi',
 'pendulo_70cm_1_reduzido.mov',
 'pendulo_70cm_1_trecho.mov',
 'pendulo_70cm_1_trecho_cinza.avi',
 'pendulo_70cm_1_trecho_com_deteccao.avi',
 'pendulo_70cm_1_trecho_pb.avi',
 'pendulo_70cm_2_reduzido.mov']

Versão curta do primeiro vídeo do pêndulo

Vamos começar com o vídeo pendulo_70cm_1_trecho.mov, que exibe apenas um trecho inicial, de uns 4 segundos, do vídeo pendulo_70cm_1_reduzido.mov, que tem 16 segundos. O termo reduzido se refere a reduções na resolução e na qualidade do vídeo original, que é de 44Mb. O original do vídeo seguinte, com 2 minutos, tem mais de 230Mb. Por isso a necessidade dessa redução.

In [7]:
HTML(data='''<video width="80%" alt="test" controls><source src="data:video/mp4;base64,{0}" type="video/mp4" />
             </video>'''.format(encoded.decode('ascii')))
Out[7]:

Executando o módulo: primeiro vídeo exibindo a detecção

Agora, executo o módulo neste vídeo, guardando os dados do movimento em uma variável dados e gravando o vídeo de saída no arquivo

In [8]:
fps, num_quadros, tracado = movimento.detector_de_movimento(path.join(videos_dir, 'pendulo_70cm_1_trecho.mov'), 
                                video_tracado=path.join(videos_dir, 'pendulo_70cm_1_trecho_com_deteccao.avi'),
                                video_cinza=path.join(videos_dir, 'pendulo_70cm_1_trecho_cinza.avi'),
                                video_pb=path.join(videos_dir, 'pendulo_70cm_1_trecho_pb.avi'))

Informações iniciais

In [9]:
print('Quadros por segundo:', fps)
print('Número de quadros:', num_quadros)
print(f"Tempo tratado de vídeo: {int(num_quadros/fps/60):3d}m {int(num_quadros/fps % 60):02d}s {int(1000*(num_quadros/fps % 1)):02d}ms")
print()
print('Dados iniciais do traçado:')
tracado[1:11]
Quadros por segundo: 29.97002997002997
Número de quadros: 119
Tempo tratado de vídeo:   0m 03s 970ms

Dados iniciais do traçado:
Out[9]:
[['quadro', 'abscissa(px)', 'ordenada(px)', 'largura(px)', 'altura(px)'],
 [16, 495, 107, 17, 49],
 [17, 494, 107, 18, 51],
 [18, 493, 107, 19, 51],
 [19, 493, 107, 19, 51],
 [20, 493, 107, 19, 50],
 [21, 493, 107, 19, 51],
 [22, 493, 108, 19, 50],
 [23, 493, 108, 19, 49],
 [24, 492, 107, 20, 51]]

Exibindo a detecção

In [10]:
video = io.open(path.join(videos_dir, 'pendulo_70cm_1_trecho_com_deteccao.avi'), 'r+b').read()
encoded = base64.b64encode(video)
HTML(data='''<video width="80%" alt="test" controls><source src="data:video/avi;base64,{0}" type="video/avi" />
             </video>'''.format(encoded.decode('ascii')))
Out[10]:

Exibindo o vídeo em escala de cinzas

In [11]:
video = io.open(path.join(videos_dir, 'pendulo_70cm_1_trecho_cinza.avi'), 'r+b').read()
encoded = base64.b64encode(video)
HTML(data='''<video width="80%" alt="test" controls><source src="data:video/avi;base64,{0}" type="video/avi" />
             </video>'''.format(encoded.decode('ascii')))
Out[11]:

Exibindo vídeo do contraste em preto e branco

In [12]:
video = io.open(path.join(videos_dir, 'pendulo_70cm_1_trecho_pb.avi'), 'r+b').read()
encoded = base64.b64encode(video)
HTML(data='''<video width="80%" alt="test" controls><source src="data:video/avi;base64,{0}" type="video/avi" />
             </video>'''.format(encoded.decode('ascii')))
Out[12]:

Gráfico dos dados

A haste do pêndulo é de $70\,\texttt{cm}$ contados da ponta de cima do fio até a centróide da bola, considerada como sendo o centro de massa do objeto, que por sua vez é tratado como um objeto pontual.

Primeiro, lemos os dados retornados pelo traçador:

In [13]:
nd = np.array(tracado[2:])
quadro = nd[:,0]
x = nd[:,1]
y = nd[:,2]
w = nd[:,3]
h = nd[:,4]

Informações da coordenada horizontal

In [14]:
plt.figure(figsize=(12,4))
plt.plot(quadro, x, '.', label='lado esquerdo')
plt.plot(quadro, x+w, '.', label='lado direito')
plt.plot(quadro, x+w/2, '-', label='média')
plt.title('Coordenada horizontal da caixa de entorno', fontsize=18)
plt.legend()
plt.show()

Informações da coordenada vertical

In [15]:
plt.figure(figsize=(12,4))
plt.plot(quadro, y, '.', label='lado superior')
plt.plot(quadro, y+h, '.', label='lado inferior')
plt.plot(quadro, y+h/2, '-', label='média')
plt.ylim(plt.ylim()[::-1])
plt.title('Dados verticais da caixa de entorno', fontsize=18)
plt.legend()
plt.show()

Versão completa dos dados do primeiro vídeo: pêndulo com 70cm de haste

Agora, não vamos exibir os vídeos, apenas capturar os dados do vídeo completo.

In [16]:
fps1, num_quadros1, tracado1 = movimento.detector_de_movimento(path.join(videos_dir, 'pendulo_70cm_1_reduzido.mov'))

Informações

In [17]:
print('Quadros por segundo:', fps1)
print('Número de quadros:', num_quadros1)
print(f"Tempo tratado de vídeo: {int(num_quadros1/fps1/60):3d}m {int(num_quadros1/fps1 % 60):02d}s {int(1000*(num_quadros1/fps1 % 1)):02d}ms")
print()
print('Dados iniciais do traçado:')
tracado1[1:11]
Quadros por segundo: 29.97002997002997
Número de quadros: 490
Tempo tratado de vídeo:   0m 16s 349ms

Dados iniciais do traçado:
Out[17]:
[['quadro', 'abscissa(px)', 'ordenada(px)', 'largura(px)', 'altura(px)'],
 [16, 495, 107, 17, 49],
 [17, 494, 107, 18, 51],
 [18, 493, 107, 19, 51],
 [19, 493, 107, 19, 51],
 [20, 493, 107, 19, 50],
 [21, 493, 107, 19, 51],
 [22, 493, 108, 19, 50],
 [23, 493, 108, 19, 49],
 [24, 492, 108, 20, 50]]

Gráfico dos dados

Novamente, a haste do pêndulo é de $70\,\texttt{cm}$ contados da ponta de cima do fio até a centróide da bola, considerada como sendo o centro de massa do objeto, que por sua vez é tratado como um objeto pontual.

In [18]:
nd1 = np.array(tracado1[2:])
quadro1 = nd1[:,0]
x1 = nd1[:,1]
y1 = nd1[:,2]
w1 = nd1[:,3]
h1 = nd1[:,4]
In [19]:
plt.figure(figsize=(12,6))
plt.plot(quadro1, x1, '.', label='lado esquerdo')
plt.plot(quadro1, x1+w1, '.', label='lado direito')
plt.plot(quadro1, x1+w1/2, '-', label='média')
plt.title('Dados horizontais da caixa de entorno', fontsize=18)
plt.legend()
plt.show()
In [20]:
plt.figure(figsize=(12,4))
plt.plot(quadro1, y1, '.', label='lado superior')
plt.plot(quadro1, y1+h1, '.', label='lado inferior')
plt.plot(quadro1, y1+h1/2, '-', label='média')
plt.ylim(plt.ylim()[::-1])
plt.title('Dados verticais da caixa de entorno - primeiro vídeo do pêndulo', fontsize=18)
plt.legend()
plt.show()

Observação: A diferença entre o nível dos "picos" no gráfico acima parece ser uma questão de perspectiva, devida a uma oscilação do pêndulo em um plano não exatamente paralelo à placa de captura de imagem da câmera.

Segundo vídeo do pêndulo - 70cm de haste

Novamente, o mesmo pêndulo, com os mesmo $70\,\texttt{cm}$ de haste, mas com a câmera em outra posição, um tempo maior de duração e com ângulo inicial um pouco diferente.

In [21]:
fps2, num_quadros2, tracado2 = movimento.detector_de_movimento(path.join(videos_dir, 'pendulo_70cm_2_reduzido.mov'))
In [22]:
print('Quadros por segundo:', fps2)
print('Número de quadros:', num_quadros2)
print(f"Tempo tratado de vídeo: {int(num_quadros2/fps2/60):3d}m {int(num_quadros2/fps2 % 60):02d}s {int(1000*(num_quadros2/fps2 % 1)):02d}ms")
print()
print('Dados iniciais do traçado:')
tracado2[1:11]
Quadros por segundo: 30.0
Número de quadros: 3632
Tempo tratado de vídeo:   2m 01s 66ms

Dados iniciais do traçado:
Out[22]:
[['quadro', 'abscissa(px)', 'ordenada(px)', 'largura(px)', 'altura(px)'],
 [130, 486, 0, 26, 37],
 [131, 486, 0, 26, 47],
 [132, 484, 0, 28, 57],
 [133, 482, 0, 30, 69],
 [134, 481, 0, 31, 80],
 [135, 480, 0, 32, 89],
 [136, 480, 0, 32, 97],
 [137, 479, 0, 33, 103],
 [138, 478, 0, 34, 109]]
In [23]:
nd2 = np.array(tracado2[2:])
quadro2 = nd2[:,0]
x2 = nd2[:,1]
y2 = nd2[:,2]
w2 = nd2[:,3]
h2 = nd2[:,4]
In [24]:
plt.figure(figsize=(12,6))
plt.plot(quadro2, x2, '.', label='lado esquerdo')
plt.plot(quadro2, x2+w2, '.', label='lado direito')
plt.plot(quadro2, x2+w2/2, '-', label='média')
plt.title('Dados horizontais da caixa de entorno - segundo vídeo do pêndulo', fontsize=18)
plt.legend()
plt.show()

Só um trecho

In [25]:
plt.figure(figsize=(12,6))

plt.plot(quadro2[200:600], x2[200:600], '.', label='lado esquerdo')
plt.plot(quadro2[200:600], x2[200:600]+w2[200:600], '.', label='lado direito')
plt.plot(quadro2[200:600], x2[200:600]+w2[200:600]/2, '-', label='média')
plt.title('Dados horizontais da caixa de entorno - segundo vídeo do pêndulo', fontsize=18)
plt.legend()
plt.show()

Só a média neste trecho

In [26]:
plt.figure(figsize=(12,6))

plt.plot(quadro2[200:600], x2[200:600]+w2[200:600]/2, '-', label='média')
plt.title('Dados verticais da caixa de entorno - segundo vídeo do pêndulo', fontsize=18)
plt.legend()
plt.show()
In [27]:
plt.figure(figsize=(12,6))

plt.plot(quadro2[200:600], y2[200:600], '.', label='lado superior')
plt.plot(quadro2[200:600], y2[200:600]+h2[200:600], '.', label='lado inferior')
plt.plot(quadro2[200:600], y2[200:600]+h2[200:600]/2, '-', label='média')
plt.ylim(plt.ylim()[::-1])
plt.title('Dados verticais da caixa de entorno - segundo vídeo do pêndulo', fontsize=18)
plt.legend()
plt.show()

Terceiro vídeo do pêndulo - 40cm de haste

Desta vez, usamos um pêndulo com $40\,\texttt{cm}$ de haste e um ângulo inicial bem maior de quase $90^\circ$ (na verdade, quase $-90^\circ$, segundo a orientação usual).

Vamos, apenas, visualizar o trecho inicial do vídeo. Deixamos o gráfico do traçado por conta de vocês.

In [28]:
fps3, num_quadros3, tracado3 = movimento.detector_de_movimento(path.join(videos_dir, 'pendulo_40cm_reduzido.mov'),
                    video_tracado = path.join(videos_dir, 'pendulo_40cm_reduzido_tracado_inicio.avi'),
                    quadro_final = 150)

Trecho inicial do vídeo

In [29]:
video = io.open(path.join(videos_dir, 'pendulo_40cm_reduzido_tracado_inicio.avi'), 'r+b').read()
encoded = base64.b64encode(video)
HTML(data='''<video width="80%" alt="test" controls><source src="data:video/avi;base64,{0}" type="video/avi" />
             </video>'''.format(encoded.decode('ascii')))
Out[29]:

Sobre o ângulo

Nos vídeos, só temos a informação do comprimento da haste do pêndulo e dos pontos, em pixels, da posição e do tamanho da caixa de entorno. É preciso passar isso para o ângulo $\theta(t)$ da posição do pêndulo. Isso pode ser feito com um pouco de geometria.

Correção da perspectiva

Na imagem da câmera, o objeto aparenta ser menor quando está mais afastado da câmera. Isso causa desvios na leitura da posição do objeto. Mais significativo para isso é o fato da câmera não estar bem alinhada perpendicularmente ao plano de oscilação do pêndulo. E o pêndulo, aliás, não oscila exatamente em um plano.

Para corrigir esse desvio, é necessário usar uma transformação linear correspondente à projeção do objeto no plano do sensor de captação de imagem da câmera. Veja 3D projection: Mathematical Formula

Projeto

Objetivo:

Estender a análise do pêndulo para o caso de oscilações com grandes amplitudes, conforme discute nesta aula e na anterior. Em particular, deve conter

  • Um ajuste, aos dados capturados pelos vídeos acima, por um modelo de EDO incluindo um ou mais termos relativo à resistência do ar.

  • Analisar a dependência do período na amplitude a partir dos dados capturados pelo vídeo e comparar com a análise teórica feita na aula anterior.

Informações para a entrega do projeto:

  • Enviar arquivo no formato de um caderno Jupyter.

  • Enviar por correio eletrônico com cópia para Alejandro Cabrera e Ricardo Rosa.

  • Enviar a mensagem com o assunto Projeto 2 de Modelagem Matemática: Pêndulo com oscilações de grande amplitude.

  • Nomear o arquivo do caderno Jupyter com o número do projeto e o nome do(a) aluno(a), e.g. Projeto2_Nome_do_Estudante.ipynb.

  • Imagens ou dados que precisem ser carregados pelo caderno devem também ser enviados por correio eletrônico, em um subdiretório.

  • Podem enviar o caderno e o subdiretório agrupados em um único arquivo .tar, .gz ou semelhante.

Organização do caderno:

O caderno Jupyter deve conter as seguintes informações:

  • Título: "Projeto 2 de Modelagem Matemática: Pêndulo com oscilações de grande amplitude"
  • Nome do estudante
  • Data de entrega
  • Objetivos
  • Descrição do projeto
  • Análise do problema (incluindo dados trabalhados, métodos utilizadas, resultados obtidos, discussão dos resultados)

Data de entrega:

  • Até quinta-feira dia 2 de maio.